package com.cruzr.opengles.map;

import android.content.Context;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import android.util.Log;

import com.cruzr.opengles.map.layer.ImageLayer;
import com.cruzr.opengles.map.layer.GridLayer;
import com.cruzr.opengles.map.layer.PointLayer;
import com.cruzr.opengles.utils.ShaderUtils;

import java.util.concurrent.locks.ReentrantLock;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

public class GLMapRender implements GLSurfaceView.Renderer {

    public final static String TAG = "GLMapRender";

    public static final float SCALE_DEFAULT = 0.3f;

    public float[] mMVPMatrix = new float[16];

    private Context mContext;
    private ReentrantLock mLock;
    private GridLayer mGridLayer;
    private PointLayer mPointLayer;
    private ImageLayer mImageLayer;

    public int mWidth;
    public int mHeight;

    private float mScale = SCALE_DEFAULT;
    private float mTranslateX = 0.0f;
    private float mTranslateY = 0.0f;
    private float mAngle = 0.0f;

    private int mProgram;
    private int mPositionHandle;
    private int mCoordinateHandle;
    private int mColorHandle;
    private int mSizeHandle;
    private int mMatrixHandle;

    public GLMapRender(Context context) {
        mContext = context;
        mGridLayer = new GridLayer();
        mPointLayer = new PointLayer();
        mImageLayer = new ImageLayer(context);
        mLock = new ReentrantLock();
    }

    private void createProgram() {
        mProgram = ShaderUtils.createProgram(mContext.getResources(),
                "map/map_vertex.glsl", "map/map_fragment.glsl");
        mPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
        mCoordinateHandle = GLES20.glGetAttribLocation(mProgram, "aCoordinate");
        mColorHandle = GLES20.glGetAttribLocation(mProgram, "aColor");
        mSizeHandle = GLES20.glGetUniformLocation(mProgram, "aPointSize");
        mMatrixHandle = GLES20.glGetUniformLocation(mProgram, "aMatrix");
    }

    public void setTranslate(float x, float y) {
        mTranslateX += x / mHeight * 2;
        mTranslateY += - y / mHeight * 2;
    }

    public float getScale() {
        return mScale;
    }

    public void setScale(float scale) {
        mScale = scale;
    }

    public void setIncreaseAngle(float angle) {
        mAngle = ((mAngle + angle) + 360) % 360;
        if (mAngle > 180) {
            mAngle = mAngle - 360;
        }
    }

    public void computeMVP() {
        Matrix.setIdentityM(mMVPMatrix, 0);
        Matrix.scaleM(mMVPMatrix, 0, mScale, mScale, 1.0f);
        Matrix.translateM(mMVPMatrix, 0, mTranslateX / mScale, mTranslateY / mScale, 0f);
        Matrix.rotateM(mMVPMatrix, 0, mAngle, 0, 0, 1);
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        mLock.lock();
        Log.i(TAG, "onSurfaceCreated");
        GLES20.glClearColor(
                GridLayer.INT_R_BG / 255f,
                GridLayer.INT_G_BG / 255f,
                GridLayer.INT_B_BG / 255f,
                1.0f);
        createProgram();
        mImageLayer.createTexture();
        computeMVP();
        mLock.unlock();
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        mWidth = width;
        mHeight = height;
        Log.i(TAG, "onSurfaceChanged ->  mWidth : " + mWidth + ", mHeight : " + mHeight);

        int x = 0;
        int y = 0;
        if (width < height) {
            x = (width - height) / 2;
            width = height;
        } else {
            y = (height - width) / 2;
            height = width;
        }

        GLES20.glViewport(x, y, width, height);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        mLock.lock();
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);

        GLES20.glEnable(GLES20.GL_BLEND);
        GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA);

        GLES20.glUseProgram(mProgram);
        GLES20.glUniformMatrix4fv(mMatrixHandle, 1, false, mMVPMatrix, 0);

        mGridLayer.drawMap(mPositionHandle, mSizeHandle, mScale);
        mImageLayer.drawMap(mPositionHandle, mCoordinateHandle);
        mPointLayer.drawMap(mPositionHandle, mSizeHandle, mScale);

        GLES20.glDisable(GLES20.GL_BLEND);

        mLock.unlock();
    }

    public void updateMap(int width, int height, byte[] pixels) {
        mLock.lock();
        mGridLayer.updateMap(width, height, pixels);
        mLock.unlock();
    }

    public void setPoint(float x, float y) {
        mLock.lock();
        float cx = (2 * x - mWidth) / mHeight;
        float cy = 1 - 2 * y / mHeight;

        float[] invMatrix = new float[16];
        Matrix.invertM(invMatrix, 0, mMVPMatrix, 0);
        float coverX = cx * invMatrix[0] + cy * invMatrix[4] + 0 *
                invMatrix[8] + 1 * invMatrix[12];
        float coverY = cx * invMatrix[1] + cy * invMatrix[4 + 1] + 0 *
                invMatrix[8 + 1] + 1 * invMatrix[12 + 1];

        mPointLayer.updatePoint(coverX, coverY);
        mLock.unlock();
    }

    public void onDestroy() {
        mImageLayer.onDestroy();
    }
}
